<?php
/**
 * 级联CActiveForm
 * <br>结合 CActiveRecordAdv 使用
 * @author Weavora Team <hello@weavora.com>
 * @link http://weavora.com
 * @copyright Copyright (c) 2011 Weavora LLC
 * @package lib.widgets
 */
Yii::import('lib.components.CDataToken');
class CActiveFormAdv extends CActiveForm
{
	
	public function init() {
		parent::init();
		if(strtolower($this->method) == 'post') {
			echo CHtml::hiddenField(CDataToken::STORE_KEY, CDataToken::genToken());
		}
	}
	/**
	 * 级联选择器
	 * @param string $name 配置名称
	 * @return CLinkageSelector
	 */
	public function linkage($name) {
		Yii::import('lib.components.linkage.CLinkageSelector');
		$config = array(
			'class' => 'CLinkageSelector',
			'name' => $name,
			'form' => $this,
		);
		return Yii::createComponent($config);
	}
	
	/**
	 * Renders an HTML label for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated label tag
	 */
	public function label($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::label($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders an HTML label for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated label tag
	 */
	public function labelEx($parentModel, $attributedPath, $htmlOptions = array())
	{
		
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::labelEx($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a text field for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated input field
	 */
	public function textField($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::textField($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a hidden field for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated input field
	 */
	public function hiddenField($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::hiddenField($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a password field for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated input field
	 */
	public function passwordField($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::passwordField($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a text area for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated text area
	 */
	public function textArea($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::textArea($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a file field for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes
	 * @return string the generated input field
	 */
	public function fileField($parentModel, $attributedPath, $htmlOptions = array())
	{
//		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
//		$hiddenHtmlOptions = array();
//		foreach(array('name', 'id') as $k) {
//			if(isset($htmlOptions[$k])) {
//				$hiddenHtmlOptions[$k] = $htmlOptions[$k];
//			}
//		}
		
		Yii::app()->controller->widget('lib.widgets.CActiveFileField', array(
			 'form' => $this,
			 'model' => $parentModel,
			 'attribute' => $attributedPath,
		));
		
//		echo CHtml::fileField($htmlOptions['name'], '', $htmlOptions)
//			.CHtml::activeHiddenField($model, $attribute, $hiddenHtmlOptions);
		//return parent::fileField($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a radio button for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated radio button
	 */
	public function radioButton($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		CHtml::resolveNameID($model, $attribute, $htmlOptions);
		return CHtml::radioButton($htmlOptions['name'], $model->$attribute ? true : false, $htmlOptions);
	}

	/**
	 * Renders a checkbox for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated check box
	 */
	public function checkBox($parentModel, $attributedPath, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::checkBox($model, $attribute, $htmlOptions);
	}

	/**
	 * Renders a dropdown list for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $data data for generating the list options (value=>display)
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated drop down list
	 */
	public function dropDownList($parentModel, $attributedPath, $data, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::dropDownList($model, $attribute, $data, $htmlOptions);
	}

	/**
	 * Renders a list box for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $data data for generating the list options (value=>display)
	 * @param array $htmlOptions additional HTML attributes.
	 * @return string the generated list box
	 */
	public function listBox($parentModel, $attributedPath, $data, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::listBox($model, $attribute, $data, $htmlOptions);
	}

	/**
	 * Renders a checkbox list for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $data value-label pairs used to generate the check box list.
	 * @param array $htmlOptions additional HTML options.
	 * @return string the generated check box list
	 */
	public function checkBoxList($parentModel, $attributedPath, $data, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::checkBoxList($model, $attribute, $data, $htmlOptions);
	}

	/**
	 * Renders a radio button list for a model attribute.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute or path to related model attribute
	 * @param array $data value-label pairs used to generate the radio button list.
	 * @param array $htmlOptions additional HTML options.
	 * @return string the generated radio button list
	 */
	public function radioButtonList($parentModel, $attributedPath, $data, $htmlOptions = array())
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		return parent::radioButtonList($model, $attribute, $data, $htmlOptions);
	}

	/**
	 * Displays the first validation error for a model attribute.
	 * This is similar to {@link CHtml::error} except that it registers the model attribute
	 * so that if its value is changed by users, an AJAX validation may be triggered.
	 * @param CModel $parentModel the parent data model
	 * @param string $attributedPath the attribute name
	 * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag.
	 * @param boolean $enableAjaxValidation whether to enable AJAX validation for the specified attribute.
	 * @param boolean $enableClientValidation whether to enable client-side validation for the specified attribute.
	 * @return string the validation result (error display or success message).
	 * @see CHtml::error
	 */
	public function error($parentModel, $attributedPath, $htmlOptions=array(), $enableAjaxValidation=true, $enableClientValidation=true)
	{
		list($model, $attribute, $htmlOptions) = self::resolveArgs($parentModel, $attributedPath, $htmlOptions);
		if(!$this->enableAjaxValidation)
			$enableAjaxValidation=false;
		if(!$this->enableClientValidation)
			$enableClientValidation=false;

		if(!isset($htmlOptions['class']))
			$htmlOptions['class']=$this->errorMessageCssClass;

		if(!$enableAjaxValidation && !$enableClientValidation)
			return CHtml::error($model,$attribute,$htmlOptions);

		CHtml::resolveNameID($model, $attribute, $htmlOptions);
		
		$id=$htmlOptions['id'];
		
		$inputID=isset($htmlOptions['inputID']) ? $htmlOptions['inputID'] : $id;
		unset($htmlOptions['inputID']);
		if($inputID == $htmlOptions['id'])
			$htmlOptions['id']=$inputID.'_em_';

		$option=array(
			'id'=>$id,
			'inputID'=>$inputID,
			'errorID'=>$htmlOptions['id'],
			'model'=>get_class($model),
			'name'=>$htmlOptions['name'],
			'enableAjaxValidation'=>$enableAjaxValidation,
		);

		$optionNames=array(
			'validationDelay',
			'validateOnChange',
			'validateOnType',
			'hideErrorMessage',
			'inputContainer',
			'errorCssClass',
			'successCssClass',
			'validatingCssClass',
			'beforeValidateAttribute',
			'afterValidateAttribute',
		);
		foreach($optionNames as $name)
		{
			if(isset($htmlOptions[$name]))
			{
				$option[$name]=$htmlOptions[$name];
				unset($htmlOptions[$name]);
			}
		}
		if($model instanceof CActiveRecord && !$model->isNewRecord)
			$option['status']=1;

		if($enableClientValidation)
		{
			$validators=isset($htmlOptions['clientValidation']) ? array($htmlOptions['clientValidation']) : array();
			foreach($model->getValidators($attribute) as $validator)
			{
				if($enableClientValidation && $validator->enableClientValidation)
				{
					if(($js=$validator->clientValidateAttribute($model,$attribute))!='')
						$validators[]=$js;
				}
			}
			if($validators!==array())
				$option['clientValidation']="js:function(value, messages, attribute) {\n".implode("\n",$validators)."\n}";
		}

		$html=CHtml::error($model,$attribute,$htmlOptions);
		if($html==='')
		{
			if(isset($htmlOptions['style']))
				$htmlOptions['style']=rtrim($htmlOptions['style'],';').';display:none';
			else
				$htmlOptions['style']='display:none';
			$html=CHtml::tag('div',$htmlOptions,'');
		}

		$this->attributes[$inputID]=$option;
		return $html;
		
	}
	
	/**
	 * Validates one or several models and returns the results in JSON format.
	 * This is a helper method that simplifies the way of writing AJAX validation code.
	 * @param mixed $models a single model instance or an array of models.
	 * @param array $attributes list of attributes that should be validated. Defaults to null,
	 * meaning any attribute listed in the applicable validation rules of the models should be
	 * validated. If this parameter is given as a list of attributes, only
	 * the listed attributes will be validated.
	 * @param boolean $loadInput whether to load the data from $_POST array in this method.
	 * If this is true, the model will be populated from <code>$_POST[ModelClass]</code>.
	 * @return string the JSON representation of the validation error messages.
	 */
	public static function validate($model, $attributes=null, $loadInput=true) {
		if($loadInput && isset($_POST[get_class($model)]))
				$model->attributes=$_POST[get_class($model)];
		$result = self::validateCascade($model, $model);
		return function_exists('json_encode') ? json_encode($result) : CJSON::encode($result);
	}
	
	public static function validateCascade($parentModel, $subModels = null, $rPath = '', $validated = false) {
		$result = array();
		$hasMany = true;
		
		if(!$subModels)
			$subModels = $parentModel;
		if(!is_array($subModels)) {
			$subModels=array($subModels);
			$hasMany = false;
		}
		if(!$validated) {
			$parentModel->validate();
			$validated = true;
		}
		
		foreach($subModels as $mIndex => $model) {
			
			$rs = $model->relations();
			foreach($model->getErrors() as $eAttr=>$eMsg) {
				$path = '';
				$subPath = $hasMany ? $mIndex.'.'.$eAttr : $eAttr;
				$path = $rPath ? $rPath.'.'.$subPath : $subPath;
				if(isset($rs[$eAttr])) {
					$result = array_merge($result, self::validateCascade($parentModel, $model->$eAttr, $path, $validated));
				} else {
					$htmlOptions = array();
					list($tModel, $tAttr, $htmlOptions) = 
					self::resolveArgs($parentModel, $path, $htmlOptions);
					CHtml::resolveNameID($model, $tAttr, $htmlOptions);
					$result[$htmlOptions['id']]=$eMsg;
				}
			}
		}
		
		return $result;
		
	}
	
	public static function resolveModel($parentModel, $attributedPath)
	{
		$model = $parentModel;
		$pathPortions = explode('.', $attributedPath);

		// last portion is always model attribute
		$attribute = array_pop($pathPortions);
		
		foreach ($pathPortions as $index => $portion) {
			// handle 'parent.statuses..id'
			if ($portion == '') {
				// portion becomes to new index
				$portion = is_array($model) ? count($model) : 0;
			}

			// handle 'parent.1.' or 'parent.n1.'
			if (is_array($model)) {
				$nextModel = isset($model[$portion]) ? $model[$portion]
						: self::createRelationModel($parentModel, $pathPortions[$index - 1]);
			}

			elseif (preg_match('/\{.*\}$/', $portion)) {
				$nextModel = self::createRelationModel($parentModel, $pathPortions[$index - 1]);
			}
				// handle 'parent.status' when status relation is empty (new model required)
			elseif (empty($model->{$portion})) {
				$nextModel = self::createRelationModel($model, $portion, true);
			}
				// handle 'parent.status'
			else {
				$nextModel = $model->{$portion};
			}

			// shift models
			$parentModel = $model;
			$model = $nextModel;
		}
		
		
		return $model;
	}

	public static function resolveName($parentModel, $attributedPath)
	{
		$name = get_class($parentModel);
		$pathPortions = explode('.', $attributedPath);
		foreach ($pathPortions as $key => $pathPortion) {
			if ($pathPortion === '')
				$pathPortion = '';
			$name .= '[' . $pathPortion . ']';
		}
		return $name;
	}

	public static function resolveAttribute($attributedPath)
	{
		$pathPortions = explode('.', $attributedPath);
		return trim(end($pathPortions));
	}

	public static function resolveArgs($parentModel, $attributedPath, $htmlOptions = array())
	{
		$model = self::resolveModel($parentModel, $attributedPath);
		$attribute = self::resolveAttribute($attributedPath);
		if (empty($htmlOptions['name']))
			$htmlOptions['name'] = self::resolveName($parentModel, $attributedPath);

		return array($model, $attribute, $htmlOptions);
	}

	public static function resolveLabel($parentModel, $attributedPath)
	{
		$attribute = str_replace('..','.', $attributedPath);
//		if($attributedPath == 'attrs.2.option.value_type') {
//			//echo get_class($model);
//			echo $parentModel->getAttributeLabel($attribute);;
//		}
		return $parentModel->getAttributeLabel($attribute);
	}

	protected static function createRelationModel($model, $relation, $allowMany = false)
	{
		$relations = $model->relations();
		if (!array_key_exists($relation, $relations))
			throw new Exception("Undefined relation " . $relation);

		$relationType = $relations[$relation][0];
		$relationModelClass = $relations[$relation][1];

		if ($allowMany && in_array($relationType, array(CActiveRecord::HAS_MANY, CActiveRecord::MANY_MANY))) {
			$model = array();
		} else {
			$model = new $relationModelClass();
		}
		return $model;
	}
}
